/*
 * Copyright (c) 2012 Johan Fylling
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights 
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
 * THE SOFTWARE.
 */

part of math;


class Vector2 extends Vector {
  double _x;
  double _y;
  
  Vector2 ([num x=0.0, num y=0.0]) {
    this._x = x.toDouble();
    this._y = y.toDouble();
  }
  
  Vector2.copy (Vector2 other) {
    this._x = other._x;
    this._y = other._y;
  }
  
  Vector2.fromAngle (num angle, [Vector2 base]) {
    base = (base != null ? base : new Vector2(0.0,1.0));
    Vector2 xAxis = new Vector2(1.0,0.0);
    num baseAngle = xAxis.angle(base);
    this._x = Math.cos(angle + baseAngle);
    this._y = Math.sin(angle + baseAngle);
  }
  
  num get x => this._x;
  
  void set x (num x) { this._x = x.toDouble(); }
  
  num get y => this._y;
  
  void set y (num y) { this._y = y.toDouble(); }
  
  void clone (Vector2 other) {
    this._x = other._x;
    this._y = other._y;
  }
  
  /**
   * Determines if this Vector has zero length.
   **/
  bool isNull () {
    return this._x == 0 && this._y == 0;
  }
  
  void setNull () {
    this._x = 0.0;
    this._y = 0.0;
  }
  
  /**
   * Get squared length of vector.
   */
  num get lengthSquared {
    return this._x * this._x +
           this._y * this._y;
  }
  
  /**
   * Get Vector length.
   */
  num get length {
    return Math.sqrt(lengthSquared);
  }
  
  /**
   * Normalize this Vector.
   */
  Vector2 normalize () {
    if (isNull()) {
      return this;
    }
    
    num len = length;
    this._x /= len;
    this._y /= len;
    return this;
  }
  
  /**
   * Get normalized copy of this Vector.
   */
  Vector2 normalized () {
    if (isNull()) {
      return this;
    }
    
    num len = length;
    return new Vector2(this._x / len,
                       this._y / len);
  }
  
  /**
   * Clamps this vector's x-component between the provided upper and lower limits.
   */
  void clampX (num lower, num upper) {
    this._x = Math.max(lower, Math.min(upper, this._x));
  }
  
  /**
   * Clamps this vector's y-component between the provided upper and lower limits.
   */
  void clampY (num lower, num upper) {
    this._y = Math.max(lower, Math.min(upper, this._y));
  }
  
  /**
   * Clamps this vector's components between the provided upper and lower limits.
   */
  void clamp (num lowerX, num upperX, num lowerY, num upperY) {
    clampX(lowerX, upperX);
    clampY(lowerY, upperY);
  }
  
  void mul (num factor) {
    this._x *= factor;
    this._y *= factor;
  }
  
  void div (num divisor) {
    this._x /= divisor;
    this._y /= divisor;
  }
  
  void add (Vector2 other) {
    this._x += other._x;
    this._y += other._y;
  }
  
  num angle (Vector2 other) {
    return Math.atan2(perpDot(other), dot(other));
  }
  
  num dot (Vector2 other) {
    return this._x*other._x + this._y*other._y;
  }
  
  num perpDot (Vector2 other) {
    return this._x*other._y - this._y*other._x;
  }
  
  Vector2 cross (Vector2 other) {
    return new Vector2(this._x * other._y, this._y * other._x);
  }
  
  Vector2 cross2 (num val) {
    return new Vector2(-1.0 * this._y * val, this._x * val);
  }
  
  void invert () {
    this._x = -this._x;
    this._y = -this._y;
  }
  
  bool operator ==(Vector2 other) {
    return other != null &&
           this._x == other._x &&
           this._y == other._y;
  }
  
  Vector2 operator -() {
    return new Vector2(-this._x, -this._y);
  }
  
  Vector2 operator +(Vector2 other) {
    return new Vector2(this._x + other._x,
                       this._y + other._y);
  }
  
  Vector2 operator -(Vector2 other) {
    return new Vector2(this._x - other._x,
                       this._y - other._y);
  }
  
  Vector2 operator *(num factor) {
    return new Vector2(this._x * factor.toDouble(), 
                       this._y * factor.toDouble());
  }
  
  Vector2 operator /(num divisor) {
    return new Vector2(this._x / divisor.toDouble(), 
                       this._y / divisor.toDouble());
  }
  
  String toString () => "($_x,$_y)";
  
  toJson () => {"x":_x,"y":_y};
}
